/*
 *
 *    Copyright (c) 2021 Project CHIP Authors
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */

#include <cstdarg>
#include <cstdio>
#include <memory>
#include <type_traits>

#include <app/BufferedReadCallback.h>
#include <app/ChunkedWriteCallback.h>
#include <app/DeviceProxy.h>
#include <app/InteractionModelEngine.h>
#include <app/ReadClient.h>
#include <app/WriteClient.h>
#include <controller/CHIPDeviceController.h>
#include <controller/python/matter/interaction_model/Delegate.h>
#include <controller/python/matter/native/PyChipError.h>
#include <lib/core/Optional.h>
#include <lib/support/CodeUtils.h>
#include <lib/support/logging/CHIPLogging.h>
#include <system/SystemClock.h>

using namespace chip;
using namespace chip::app;

using PyObject = void;

namespace chip {
namespace python {

struct __attribute__((packed)) AttributePath
{
    chip::EndpointId endpointId;
    chip::ClusterId clusterId;
    chip::AttributeId attributeId;
    chip::DataVersion dataVersion;
    uint8_t hasDataVersion;
};

struct __attribute__((packed)) EventPath
{
    chip::EndpointId endpointId;
    chip::ClusterId clusterId;
    chip::EventId eventId;
    uint8_t urgentEvent;
};

struct __attribute__((packed)) DataVersionFilter
{
    chip::EndpointId endpointId;
    chip::ClusterId clusterId;
    chip::DataVersion dataVersion;
};

using OnReadAttributeDataCallback       = void (*)(PyObject * appContext, chip::DataVersion version, chip::EndpointId endpointId,
                                             chip::ClusterId clusterId, chip::AttributeId attributeId,
                                             std::underlying_type_t<Protocols::InteractionModel::Status> imstatus, uint8_t * data,
                                             size_t dataLen);
using OnReadEventDataCallback           = void (*)(PyObject * appContext, chip::EndpointId endpointId, chip::ClusterId clusterId,
                                         chip::EventId eventId, chip::EventNumber eventNumber, uint8_t priority, uint64_t timestamp,
                                         uint8_t timestampType, uint8_t * data, size_t dataLen,
                                         std::underlying_type_t<Protocols::InteractionModel::Status> imstatus);
using OnSubscriptionEstablishedCallback = void (*)(PyObject * appContext, SubscriptionId subscriptionId);
using OnResubscriptionAttemptedCallback = void (*)(PyObject * appContext, PyChipError aTerminationCause,
                                                   uint32_t aNextResubscribeIntervalMsec);
using OnReadErrorCallback               = void (*)(PyObject * appContext, PyChipError chiperror);
using OnReadDoneCallback                = void (*)(PyObject * appContext);
using OnReportBeginCallback             = void (*)(PyObject * appContext);
using OnReportEndCallback               = void (*)(PyObject * appContext);

OnReadAttributeDataCallback gOnReadAttributeDataCallback             = nullptr;
OnReadEventDataCallback gOnReadEventDataCallback                     = nullptr;
OnSubscriptionEstablishedCallback gOnSubscriptionEstablishedCallback = nullptr;
OnResubscriptionAttemptedCallback gOnResubscriptionAttemptedCallback = nullptr;
OnReadErrorCallback gOnReadErrorCallback                             = nullptr;
OnReadDoneCallback gOnReadDoneCallback                               = nullptr;
OnReportBeginCallback gOnReportBeginCallback                         = nullptr;
OnReportBeginCallback gOnReportEndCallback                           = nullptr;

void PythonResubscribePolicy(uint32_t aNumCumulativeRetries, uint32_t & aNextSubscriptionIntervalMsec, bool & aShouldResubscribe)
{
    aShouldResubscribe = true;
}

class ReadClientCallback : public ReadClient::Callback
{
public:
    ReadClientCallback(PyObject * appContext, bool allowLargePayload) :
        mBufferedReadCallback(*this, allowLargePayload), mAppContext(appContext)
    {}

    app::BufferedReadCallback * GetBufferedReadCallback() { return &mBufferedReadCallback; }

    void OnAttributeData(const ConcreteDataAttributePath & aPath, TLV::TLVReader * apData, const StatusIB & aStatus) override
    {
        //
        // We shouldn't be getting list item operations in the provided path since that should be handled by the buffered read
        // callback. If we do, that's a bug.
        //
        VerifyOrDie(!aPath.IsListItemOperation());

        std::unique_ptr<uint8_t[]> buffer;
        size_t size = 0;

        // When the apData is nullptr, means we did not receive a valid attribute data from server, status will be some error
        // status.
        if (apData != nullptr)
        {
            size_t bufferLen = apData->GetRemainingLength() + apData->GetLengthRead();
            buffer           = std::make_unique<uint8_t[]>(bufferLen);
            // The TLVReader's read head is not pointing to the first element in the container instead of the container itself, use
            // a TLVWriter to get a TLV with a normalized TLV buffer (Wrapped with a anonymous tag, no extra "end of container" tag
            // at the end.)
            TLV::TLVWriter writer;
            writer.Init(buffer.get(), bufferLen);
            CHIP_ERROR err = writer.CopyElement(TLV::AnonymousTag(), *apData);
            if (err != CHIP_NO_ERROR)
            {
                this->OnError(err);
                return;
            }
            size = writer.GetLengthWritten();
        }

        DataVersion version = 0;
        if (aPath.mDataVersion.HasValue())
        {
            version = aPath.mDataVersion.Value();
        }

        gOnReadAttributeDataCallback(mAppContext, version, aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId,
                                     to_underlying(aStatus.mStatus), buffer.get(), size);
    }

    void OnSubscriptionEstablished(SubscriptionId aSubscriptionId) override
    {
        // Only enable auto resubscribe if the subscription is established successfully.
        mAutoResubscribeNeeded = mAutoResubscribe;
        gOnSubscriptionEstablishedCallback(mAppContext, aSubscriptionId);
    }

    CHIP_ERROR OnResubscriptionNeeded(ReadClient * apReadClient, CHIP_ERROR aTerminationCause) override
    {
        if (mAutoResubscribeNeeded)
        {
            ReturnErrorOnFailure(ReadClient::Callback::OnResubscriptionNeeded(apReadClient, aTerminationCause));
        }
        gOnResubscriptionAttemptedCallback(mAppContext, ToPyChipError(aTerminationCause),
                                           apReadClient->ComputeTimeTillNextSubscription());
        if (mAutoResubscribeNeeded)
        {
            return CHIP_NO_ERROR;
        }
        return aTerminationCause;
    }

    void OnEventData(const EventHeader & aEventHeader, TLV::TLVReader * apData, const StatusIB * apStatus) override
    {
        uint8_t buffer[CHIP_CONFIG_DEFAULT_UDP_MTU_SIZE];
        size_t size    = 0;
        CHIP_ERROR err = CHIP_NO_ERROR;
        // When the apData is nullptr, means we did not receive a valid event data from server, status will be some error
        // status.
        if (apData != nullptr)
        {
            // The TLVReader's read head is not pointing to the first element in the container instead of the container itself, use
            // a TLVWriter to get a TLV with a normalized TLV buffer (Wrapped with a anonymous tag, no extra "end of container" tag
            // at the end.)
            TLV::TLVWriter writer;
            writer.Init(buffer);
            err = writer.CopyElement(TLV::AnonymousTag(), *apData);
            if (err != CHIP_NO_ERROR)
            {
                this->OnError(err);
                return;
            }
            size = writer.GetLengthWritten();
        }
        else if (apStatus != nullptr)
        {
            size = 0;
        }
        else
        {
            err = CHIP_ERROR_INCORRECT_STATE;
            this->OnError(err);
        }

        gOnReadEventDataCallback(
            mAppContext, aEventHeader.mPath.mEndpointId, aEventHeader.mPath.mClusterId, aEventHeader.mPath.mEventId,
            aEventHeader.mEventNumber, to_underlying(aEventHeader.mPriorityLevel), aEventHeader.mTimestamp.mValue,
            to_underlying(aEventHeader.mTimestamp.mType), buffer, size,
            to_underlying(apStatus == nullptr ? Protocols::InteractionModel::Status::Success : apStatus->mStatus));
    }

    void OnError(CHIP_ERROR aError) override { gOnReadErrorCallback(mAppContext, ToPyChipError(aError)); }

    void OnReportBegin() override { gOnReportBeginCallback(mAppContext); }
    void OnDeallocatePaths(chip::app::ReadPrepareParams && aReadPrepareParams) override
    {
        if (aReadPrepareParams.mpAttributePathParamsList != nullptr)
        {
            delete[] aReadPrepareParams.mpAttributePathParamsList;
        }

        if (aReadPrepareParams.mpEventPathParamsList != nullptr)
        {
            delete[] aReadPrepareParams.mpEventPathParamsList;
        }

        if (aReadPrepareParams.mpDataVersionFilterList != nullptr)
        {
            delete[] aReadPrepareParams.mpDataVersionFilterList;
        }
    }

    void OnReportEnd() override { gOnReportEndCallback(mAppContext); }

    void OnDone(ReadClient *) override
    {
        gOnReadDoneCallback(mAppContext);

        delete this;
    };

    void AdoptReadClient(std::unique_ptr<ReadClient> apReadClient) { mReadClient = std::move(apReadClient); }

    void SetAutoResubscribe(bool autoResubscribe) { mAutoResubscribe = autoResubscribe; }

private:
    BufferedReadCallback mBufferedReadCallback;

    PyObject * mAppContext;

    std::unique_ptr<ReadClient> mReadClient;
    bool mAutoResubscribe       = true;
    bool mAutoResubscribeNeeded = false;
};

extern "C" {

struct __attribute__((packed)) PyReadAttributeParams
{
    uint16_t minInterval; // MinInterval in subscription request
    uint16_t maxInterval; // MaxInterval in subscription request
    bool isSubscription;
    bool isFabricFiltered;
    bool keepSubscriptions;
    bool autoResubscribe;
};

PyChipError pychip_WriteClient_WriteAttributes(void * appContext, DeviceProxy * device, size_t timedWriteTimeoutMsSizeT,
                                               size_t interactionTimeoutMsSizeT, size_t busyWaitMsSizeT,
                                               chip::python::PyWriteAttributeData * writeAttributesData, size_t attributeDataLength,
                                               bool forceLegacyListEncoding);
PyChipError pychip_WriteClient_TestOnlyWriteAttributesWithMismatchedTimedRequestField(
    void * appContext, DeviceProxy * device, size_t timedWriteTimeoutMsSizeT, bool timedRequestFieldValue,
    size_t interactionTimeoutMsSizeT, size_t busyWaitMsSizeT, chip::python::PyWriteAttributeData * writeAttributesData,
    size_t attributeDataLength);
PyChipError pychip_WriteClient_WriteGroupAttributes(size_t groupIdSizeT, chip::Controller::DeviceCommissioner * devCtrl,
                                                    size_t busyWaitMsSizeT,
                                                    chip::python::PyWriteAttributeData * writeAttributesData,
                                                    size_t attributeDataLength);
}

using OnWriteResponseCallback = void (*)(PyObject * appContext, chip::EndpointId endpointId, chip::ClusterId clusterId,
                                         chip::AttributeId attributeId,
                                         std::underlying_type_t<Protocols::InteractionModel::Status> imstatus);
using OnWriteErrorCallback    = void (*)(PyObject * appContext, PyChipError chiperror);
using OnWriteDoneCallback     = void (*)(PyObject * appContext);

OnWriteResponseCallback gOnWriteResponseCallback = nullptr;
OnWriteErrorCallback gOnWriteErrorCallback       = nullptr;
OnWriteDoneCallback gOnWriteDoneCallback         = nullptr;

class WriteClientCallback : public WriteClient::Callback
{
public:
    WriteClientCallback(PyObject * appContext) : mCallback(this), mAppContext(appContext) {}

    WriteClient::Callback * GetChunkedCallback() { return &mCallback; }

    void OnResponse(const WriteClient * apWriteClient, const ConcreteDataAttributePath & aPath, app::StatusIB aStatus) override
    {
        gOnWriteResponseCallback(mAppContext, aPath.mEndpointId, aPath.mClusterId, aPath.mAttributeId,
                                 to_underlying(aStatus.mStatus));
    }

    void OnError(const WriteClient * apWriteClient, CHIP_ERROR aProtocolError) override
    {
        gOnWriteErrorCallback(mAppContext, ToPyChipError(aProtocolError));
    }

    void OnDone(WriteClient * apWriteClient) override
    {
        gOnWriteDoneCallback(mAppContext);
        delete apWriteClient;
        delete this;
    };

private:
    ChunkedWriteCallback mCallback;
    PyObject * mAppContext = nullptr;
};

} // namespace python
} // namespace chip

using namespace chip::python;

namespace {
// Helper function to process write attributes data - reduces code duplication
CHIP_ERROR ProcessWriteAttributesData(WriteClient * client, python::PyWriteAttributeData * writeAttributesData,
                                      size_t attributeDataLength, bool forceLegacyListEncoding = false)
{
    CHIP_ERROR err = CHIP_NO_ERROR;

    for (size_t i = 0; i < attributeDataLength; i++)
    {
        python::PyAttributePath path = writeAttributesData[i].attributePath;
        void * tlv                   = writeAttributesData[i].tlvData;
        size_t length                = writeAttributesData[i].tlvLength;

        uint8_t * tlvBuffer = reinterpret_cast<uint8_t *>(tlv);

        TLV::TLVReader reader;
        reader.Init(tlvBuffer, static_cast<uint32_t>(length));
        TEMPORARY_RETURN_IGNORED reader.Next();
        Optional<DataVersion> dataVersion;
        if (path.hasDataVersion == 1)
        {
            dataVersion.SetValue(path.dataVersion);
        }

        if (forceLegacyListEncoding)
        {
            auto listEncodingOverride = WriteClient::TestListEncodingOverride::kForceLegacyEncoding;
            SuccessOrExit(err = client->PutPreencodedAttribute(
                              chip::app::ConcreteDataAttributePath(path.endpointId, path.clusterId, path.attributeId, dataVersion),
                              reader, listEncodingOverride));
        }
        else
        {
            SuccessOrExit(
                err = client->PutPreencodedAttribute(
                    chip::app::ConcreteDataAttributePath(path.endpointId, path.clusterId, path.attributeId, dataVersion), reader));
        }
    }

exit:
    return err;
}
} // namespace

extern "C" {
void pychip_WriteClient_InitCallbacks(OnWriteResponseCallback onWriteResponseCallback, OnWriteErrorCallback onWriteErrorCallback,
                                      OnWriteDoneCallback onWriteDoneCallback)
{
    gOnWriteResponseCallback = onWriteResponseCallback;
    gOnWriteErrorCallback    = onWriteErrorCallback;
    gOnWriteDoneCallback     = onWriteDoneCallback;
}

void pychip_ReadClient_InitCallbacks(OnReadAttributeDataCallback onReadAttributeDataCallback,
                                     OnReadEventDataCallback onReadEventDataCallback,
                                     OnSubscriptionEstablishedCallback onSubscriptionEstablishedCallback,
                                     OnResubscriptionAttemptedCallback onResubscriptionAttemptedCallback,
                                     OnReadErrorCallback onReadErrorCallback, OnReadDoneCallback onReadDoneCallback,
                                     OnReportBeginCallback onReportBeginCallback, OnReportEndCallback onReportEndCallback)
{
    gOnReadAttributeDataCallback       = onReadAttributeDataCallback;
    gOnReadEventDataCallback           = onReadEventDataCallback;
    gOnSubscriptionEstablishedCallback = onSubscriptionEstablishedCallback;
    gOnResubscriptionAttemptedCallback = onResubscriptionAttemptedCallback;
    gOnReadErrorCallback               = onReadErrorCallback;
    gOnReadDoneCallback                = onReadDoneCallback;
    gOnReportBeginCallback             = onReportBeginCallback;
    gOnReportEndCallback               = onReportEndCallback;
}

PyChipError pychip_WriteClient_WriteAttributes(void * appContext, DeviceProxy * device, size_t timedWriteTimeoutMsSizeT,
                                               size_t interactionTimeoutMsSizeT, size_t busyWaitMsSizeT,
                                               python::PyWriteAttributeData * writeAttributesData, size_t attributeDataLength,
                                               bool forceLegacyListEncoding)
{
    CHIP_ERROR err = CHIP_NO_ERROR;

    // The FFI from Python to C when calling a variadic function has issues when the regular, non-variadic, function
    // arguments are unit16_t. As a result we pass these arguments as size_t and cast them to the expected uint16_t.
    uint16_t timedWriteTimeoutMs  = static_cast<uint16_t>(timedWriteTimeoutMsSizeT);
    uint16_t interactionTimeoutMs = static_cast<uint16_t>(interactionTimeoutMsSizeT);
    uint16_t busyWaitMs           = static_cast<uint16_t>(busyWaitMsSizeT);

    std::unique_ptr<WriteClientCallback> callback = std::make_unique<WriteClientCallback>(appContext);
    std::unique_ptr<WriteClient> client           = std::make_unique<WriteClient>(
        app::InteractionModelEngine::GetInstance()->GetExchangeManager(), callback->GetChunkedCallback(),
        timedWriteTimeoutMs != 0 ? Optional<uint16_t>(timedWriteTimeoutMs) : Optional<uint16_t>::Missing());

    VerifyOrExit(device != nullptr && device->GetSecureSession().HasValue(), err = CHIP_ERROR_MISSING_SECURE_SESSION);

    SuccessOrExit(err =
                      ProcessWriteAttributesData(client.get(), writeAttributesData, attributeDataLength, forceLegacyListEncoding));

    SuccessOrExit(err = client->SendWriteRequest(device->GetSecureSession().Value(),
                                                 interactionTimeoutMs != 0 ? System::Clock::Milliseconds32(interactionTimeoutMs)
                                                                           : System::Clock::kZero));

    client.release();
    callback.release();

    if (busyWaitMs)
    {
        usleep(busyWaitMs * 1000);
    }

exit:
    return ToPyChipError(err);
}

PyChipError pychip_WriteClient_TestOnlyWriteAttributesWithMismatchedTimedRequestField(
    void * appContext, DeviceProxy * device, size_t timedWriteTimeoutMsSizeT, bool timedRequestFieldValue,
    size_t interactionTimeoutMsSizeT, size_t busyWaitMsSizeT, python::PyWriteAttributeData * writeAttributesData,
    size_t attributeDataLength)
{
#if CONFIG_BUILD_FOR_HOST_UNIT_TEST
    CHIP_ERROR err = CHIP_NO_ERROR;

    uint16_t timedWriteTimeoutMs  = static_cast<uint16_t>(timedWriteTimeoutMsSizeT);
    uint16_t interactionTimeoutMs = static_cast<uint16_t>(interactionTimeoutMsSizeT);
    uint16_t busyWaitMs           = static_cast<uint16_t>(busyWaitMsSizeT);

    std::unique_ptr<WriteClientCallback> callback = std::make_unique<WriteClientCallback>(appContext);

    // Use the TestOnly constructor that allows decoupling the Timed Request action from the TimedRequest field value.
    // This allows testing mismatched scenarios:
    // - timedWriteTimeoutMs = 0, timedRequestFieldValue = true:  No action, but field=true (TIMED_REQUEST_MISMATCH)
    // - timedWriteTimeoutMs > 0, timedRequestFieldValue = false: Action sent, but field=false (TIMED_REQUEST_MISMATCH)
    std::unique_ptr<WriteClient> client = std::make_unique<WriteClient>(
        app::InteractionModelEngine::GetInstance()->GetExchangeManager(), callback->GetChunkedCallback(),
        timedWriteTimeoutMs != 0 ? Optional<uint16_t>(timedWriteTimeoutMs) : Optional<uint16_t>::Missing(), timedRequestFieldValue,
        WriteClient::TestOnlyOverrideTimedRequestFieldTag{});

    VerifyOrExit(device != nullptr && device->GetSecureSession().HasValue(), err = CHIP_ERROR_MISSING_SECURE_SESSION);

    SuccessOrExit(err = ProcessWriteAttributesData(client.get(), writeAttributesData, attributeDataLength));

    // Send WriteRequest - will trigger TIMED_REQUEST_MISMATCH if action and field don't match
    SuccessOrExit(err = client->SendWriteRequest(device->GetSecureSession().Value(),
                                                 interactionTimeoutMs != 0 ? System::Clock::Milliseconds32(interactionTimeoutMs)
                                                                           : System::Clock::kZero));

    client.release();
    callback.release();

    if (busyWaitMs)
    {
        usleep(busyWaitMs * 1000);
    }

exit:
    return ToPyChipError(err);
#else
    return ToPyChipError(CHIP_ERROR_NOT_IMPLEMENTED);
#endif
}

PyChipError pychip_WriteClient_WriteGroupAttributes(size_t groupIdSizeT, chip::Controller::DeviceCommissioner * devCtrl,
                                                    size_t busyWaitMsSizeT, python::PyWriteAttributeData * writeAttributesData,
                                                    size_t attributeDataLength)
{
    CHIP_ERROR err = CHIP_NO_ERROR;

    // The FFI from Python to C when calling a variadic function has issues when the regular, non-variadic, function
    // arguments are unit16_t (which is the type for chip::GroupId). As a result we pass these arguments as size_t
    // and cast them to the expected type here.
    chip::GroupId groupId = static_cast<chip::GroupId>(groupIdSizeT);
    uint16_t busyWaitMs   = static_cast<uint16_t>(busyWaitMsSizeT);

    chip::Messaging::ExchangeManager * exchangeManager = chip::app::InteractionModelEngine::GetInstance()->GetExchangeManager();
    VerifyOrReturnError(exchangeManager != nullptr, ToPyChipError(CHIP_ERROR_INCORRECT_STATE));

    std::unique_ptr<WriteClient> client = std::make_unique<WriteClient>(
        app::InteractionModelEngine::GetInstance()->GetExchangeManager(), nullptr /* callback */, Optional<uint16_t>::Missing());

    for (size_t i = 0; i < attributeDataLength; i++)
    {
        python::PyAttributePath path = writeAttributesData[i].attributePath;
        void * tlv                   = writeAttributesData[i].tlvData;
        size_t length                = writeAttributesData[i].tlvLength;

        uint8_t * tlvBuffer = reinterpret_cast<uint8_t *>(tlv);

        TLV::TLVReader reader;
        reader.Init(tlvBuffer, static_cast<uint32_t>(length));
        TEMPORARY_RETURN_IGNORED reader.Next();
        Optional<DataVersion> dataVersion;
        if (path.hasDataVersion == 1)
        {
            dataVersion.SetValue(path.dataVersion);
        }
        // Using kInvalidEndpointId as that used when sending group write requests.
        SuccessOrExit(
            err = client->PutPreencodedAttribute(
                chip::app::ConcreteDataAttributePath(kInvalidEndpointId, path.clusterId, path.attributeId, dataVersion), reader));
    }

    {
        auto fabricIndex = devCtrl->GetFabricIndex();

        chip::Transport::OutgoingGroupSession session(groupId, fabricIndex);
        SuccessOrExit(err = client->SendWriteRequest(chip::SessionHandle(session), System::Clock::kZero));
    }

    if (busyWaitMs)
    {
        usleep(busyWaitMs * 1000);
    }

exit:
    return ToPyChipError(err);
}

void pychip_ReadClient_ShutdownSubscription(ReadClient * apReadClient)
{
    // If apReadClient is nullptr, it means that its life cycle has ended (such as an error happend), and nothing needs to be done.
    VerifyOrReturn(apReadClient != nullptr);
    // If it is not SubscriptionType, this function should not be executed.
    VerifyOrDie(apReadClient->IsSubscriptionType());

    Optional<SubscriptionId> subscriptionId = apReadClient->GetSubscriptionId();
    VerifyOrDie(subscriptionId.HasValue());

    FabricIndex fabricIndex = apReadClient->GetFabricIndex();
    NodeId nodeId           = apReadClient->GetPeerNodeId();

    TEMPORARY_RETURN_IGNORED InteractionModelEngine::GetInstance()->ShutdownSubscription(ScopedNodeId(nodeId, fabricIndex),
                                                                                         subscriptionId.Value());
}

void pychip_ReadClient_OverrideLivenessTimeout(ReadClient * pReadClient, uint32_t livenessTimeoutMs)
{
    VerifyOrDie(pReadClient != nullptr);
    pReadClient->OverrideLivenessTimeout(System::Clock::Milliseconds32(livenessTimeoutMs));
}

void pychip_ReadClient_TriggerResubscribeIfScheduled(ReadClient * pReadClient, const char * reason)
{
    VerifyOrDie(pReadClient != nullptr);
    pReadClient->TriggerResubscribeIfScheduled(reason);
}

PyChipError pychip_ReadClient_GetReportingIntervals(ReadClient * pReadClient, uint16_t * minIntervalSec, uint16_t * maxIntervalSec)
{
    VerifyOrDie(pReadClient != nullptr);
    CHIP_ERROR err = pReadClient->GetReportingIntervals(*minIntervalSec, *maxIntervalSec);

    return ToPyChipError(err);
}

void pychip_ReadClient_GetSubscriptionTimeoutMs(ReadClient * pReadClient, uint32_t * milliSec)
{
    VerifyOrDie(pReadClient != nullptr);

    Optional<System::Clock::Timeout> duration = pReadClient->GetSubscriptionTimeout();

    // The return value of GetSubscriptionTimeout cannot be 0
    // so milliSec=0 can be considered as the subscription has been abnormal.
    *milliSec = 0;
    if (duration.HasValue())
    {
        System::Clock::Milliseconds32 msec = std::chrono::duration_cast<System::Clock::Milliseconds32>(duration.Value());
        *milliSec                          = msec.count();
    }
}

PyChipError pychip_ReadClient_Read(void * appContext, ReadClient ** pReadClient, DeviceProxy * device, uint8_t * readParamsBuf,
                                   void ** attributePathsFromPython, size_t numAttributePaths, void ** dataversionFiltersFromPython,
                                   size_t numDataversionFilters, void ** eventPathsFromPython, size_t numEventPaths,
                                   uint64_t * eventNumberFilter, bool allowLargePayload)
{
    CHIP_ERROR err                 = CHIP_NO_ERROR;
    PyReadAttributeParams pyParams = {};
    // The readParamsBuf might be not aligned, using a memcpy to avoid some unexpected behaviors.
    memcpy(&pyParams, readParamsBuf, sizeof(pyParams));

    auto callback           = std::make_unique<ReadClientCallback>(appContext, allowLargePayload);
    auto attributePaths     = std::make_unique<AttributePathParams[]>(numAttributePaths);
    auto dataVersionFilters = std::make_unique<chip::app::DataVersionFilter[]>(numDataversionFilters);
    auto eventPaths         = std::make_unique<EventPathParams[]>(numEventPaths);
    std::unique_ptr<ReadClient> readClient;

    for (size_t i = 0; i < numAttributePaths; i++)
    {
        void * path = attributePathsFromPython[i];

        python::AttributePath pathObj;
        memcpy(&pathObj, path, sizeof(python::AttributePath));

        attributePaths[i] = AttributePathParams(pathObj.endpointId, pathObj.clusterId, pathObj.attributeId);
    }

    for (size_t i = 0; i < numDataversionFilters; i++)
    {
        void * filter = dataversionFiltersFromPython[i];

        python::DataVersionFilter filterObj;
        memcpy(&filterObj, filter, sizeof(python::DataVersionFilter));

        dataVersionFilters[i] = chip::app::DataVersionFilter(filterObj.endpointId, filterObj.clusterId, filterObj.dataVersion);
    }

    for (size_t i = 0; i < numEventPaths; i++)
    {
        void * path = eventPathsFromPython[i];

        python::EventPath pathObj;
        memcpy(&pathObj, path, sizeof(python::EventPath));

        eventPaths[i] = EventPathParams(pathObj.endpointId, pathObj.clusterId, pathObj.eventId, pathObj.urgentEvent == 1);
    }

    Optional<SessionHandle> session = device->GetSecureSession();
    VerifyOrExit(session.HasValue(), err = CHIP_ERROR_NOT_CONNECTED);

    readClient = std::make_unique<ReadClient>(
        InteractionModelEngine::GetInstance(), device->GetExchangeManager(), *callback->GetBufferedReadCallback(),
        pyParams.isSubscription ? ReadClient::InteractionType::Subscribe : ReadClient::InteractionType::Read);
    VerifyOrExit(readClient != nullptr, err = CHIP_ERROR_NO_MEMORY);
    {
        ReadPrepareParams params(session.Value());
        if (numAttributePaths != 0)
        {
            params.mpAttributePathParamsList    = attributePaths.get();
            params.mAttributePathParamsListSize = numAttributePaths;
        }
        if (numDataversionFilters != 0)
        {
            params.mpDataVersionFilterList    = dataVersionFilters.get();
            params.mDataVersionFilterListSize = numDataversionFilters;
        }
        if (numEventPaths != 0)
        {
            params.mpEventPathParamsList    = eventPaths.get();
            params.mEventPathParamsListSize = numEventPaths;
        }
        if (eventNumberFilter != nullptr)
        {
            static_assert(sizeof(chip::EventNumber) == sizeof(*eventNumberFilter) &&
                              std::is_unsigned<chip::EventNumber>::value ==
                                  std::is_unsigned<std::remove_pointer<decltype(eventNumberFilter)>::type>::value,
                          "EventNumber type mismatch");
            params.mEventNumber = MakeOptional(EventNumber(*eventNumberFilter));
        }

        params.mIsFabricFiltered = pyParams.isFabricFiltered;

        if (pyParams.isSubscription)
        {
            params.mMinIntervalFloorSeconds   = pyParams.minInterval;
            params.mMaxIntervalCeilingSeconds = pyParams.maxInterval;
            params.mKeepSubscriptions         = pyParams.keepSubscriptions;
            callback->SetAutoResubscribe(pyParams.autoResubscribe);

#if CONFIG_BUILD_FOR_HOST_UNIT_TEST
            if (!pyParams.autoResubscribe)
            {
                // We want to allow certain kinds of spec-invalid subscriptions so we
                // can test how the server reacts to them.
                err = readClient->SendSubscribeRequestWithoutValidation(params);
            }
            else
#endif // CONFIG_BUILD_FOR_HOST_UNIT_TEST
            {
                dataVersionFilters.release();
                attributePaths.release();
                eventPaths.release();

                err = readClient->SendAutoResubscribeRequest(std::move(params));
            }
            SuccessOrExit(err);
        }
        else
        {
            err = readClient->SendRequest(params);
            SuccessOrExit(err);
        }
    }

    *pReadClient = readClient.get();

    callback->AdoptReadClient(std::move(readClient));

    callback.release();

exit:
    return ToPyChipError(err);
}
}
